package hk.guosen.weix.svlt;

import hk.guosen.weix.EnCodingType;
import hk.guosen.weix.SignUtil;
import hk.guosen.weix.svc.AppService;
import hk.guosen.weix.svc.WeixServiceImpl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 微信的SERVLET处理基类，应用程序开发者可从类继承。
 * 通过实现抽象方法提供具体的应用服务，即可接入微信。
 * 
 * 
 * @author rikky.cai
 * @qq:6687523
 * @Email:6687523@qq.com
 *
 */
public abstract class WeixServlet extends HttpServlet
{	
	private static final long serialVersionUID = 1L;

	private static Logger logger = LoggerFactory.getLogger(WeixServlet.class);
	
	protected WeixServiceImpl weixSvc;
	
	protected SignUtil signUtil;
	
	public WeixServlet()
	{
		this.weixSvc = new WeixServiceImpl(createAppService(), getDefaultRespMsg());
		this.signUtil = new SignUtil(getAppToken());
	}
	
	/**
	 * 获得默认的应答消息。
	 * 当客户发送了无法识别的消息时，则返回此消息给客户。如果为null,则不处理无法识别的消息。
	 * 
	 * @return
	 */
	public String getDefaultRespMsg()
	{
		return "亲，能换个说法吗？听不懂啊。";
	}
	
	/**
	 * 获的应用的AppToken，AppToken即用户向微信申请帐号时跟URL对应的TOKEN，用于验证微信消息的真实性。具体可参考微信公共平台开发中验证消息真实性部分。
	 * 
	 * 在开发者首次提交验证申请时，微信服务器将发送GET请求到填写的URL上，并且带上四个参数（signature、timestamp、nonce、echostr），
	 * 开发者通过对签名（即signature）的效验，来判断此条消息的真实性。 
	 * 此后，每次开发者接收用户消息的时候，微信也都会带上前面三个参数（signature、timestamp、nonce）访问开发者设置的URL，
	 * 开发者依然通过对签名的效验判断此条消息的真实性。效验方式与首次提交验证申请一致。 
	 * 开发者通过检验signature对请求进行校验（下面有校验方式）。若确认此次GET请求来自微信服务器，请原样返回echostr参数内容，则接入生效，成为开发者成功，否则接入失败。 
	 * 
	 * @return
	 */
	public abstract String getAppToken();

	/**
	 * 创建应用服务。当微信的消息过来时，将会交给应用服务处理。并将其处理结果返回给微信服务器。
	 * 可以实现AppService接口或直接继承抽象子类如BaseAppService，CORAppService，处理应用服务。
	 * 
	 * @return
	 */
	public abstract AppService createAppService();

	/**
	 * 验证微信消息的真实性
	 * 
	 * 在开发者首次提交验证申请时，微信服务器将发送GET请求到填写的URL上，
	 * 并且带上四个参数（signature、timestamp、nonce、echostr），
	 * 开发者通过对签名（即signature）的效验，来判断此条消息的真实性。 
	 * 此后，每次开发者接收用户消息的时候，微信也都会带上前面三个参数（signature、timestamp、nonce）
	 * 访问开发者设置的URL，开发者依然通过对签名的效验判断此条消息的真实性。
	 * 效验方式与首次提交验证申请一致。 
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
	{
		// 微信加密签名
		String signature = request.getParameter("signature");
		// 时间戳
		String timestamp = request.getParameter("timestamp");
		// 随机数
		String nonce = request.getParameter("nonce");
		// 随机字符串
		String echostr = request.getParameter("echostr");

		// 通过检验signature对请求进行校验，若校验成功则原样返回echostr，表示接入成功，否则接入失败
		if (signUtil.checkSignature(signature, timestamp, nonce)) 
		{
			try
			{
				response.getWriter().print(echostr);
				response.getWriter().flush();
			}
			finally
			{
				response.getWriter().close();
			}
		}
		else
		{
			logger.error("checkSignature failed for request address:{}", request.getRemoteAddr());
			logger.error("signature={},timestamp={},nonce={},echostr={}", signature, timestamp, nonce, echostr);
		}
	}
	
	private String getHttpBody(HttpServletRequest request) throws IOException
	{
        String charset = request.getCharacterEncoding(); 
        if (charset == null) 
        { 
            charset = EnCodingType.UTF_8; 
        } 
        
        BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream(), charset));
        String line = null;
        StringBuilder result = new StringBuilder();
        while((line = in.readLine()) != null)
        {
        	result.append(line);
        }
        
        in.close();
        
        return result.toString();
	}

	/**
	 * 处理微信服务器发来的消息
	 */
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
	{
		String reqBody = getHttpBody(request);
		
		String result = weixSvc.onMsg(reqBody);
		
		if(result != null)
		{
			response.setCharacterEncoding(EnCodingType.UTF_8);
			response.getWriter().write(result);
			response.getWriter().close();
		}

	}

}
